HEX
Server: Apache/2.4.68 (Debian)
System: Linux as-cs-widget-demo-us-central1 6.1.0-44-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09) x86_64
User: root (0)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: /var/www/kevin-demo/wp-content/plugins/grocerslist/includes/Frontend/EmailVerificationPage.php
<?php

namespace GrocersList\Frontend;

use GrocersList\Service\ApiClient;
use GrocersList\Settings\PluginSettings;
use GrocersList\Support\Logger;

class EmailVerificationPage
{
    private const NONCE_ACTION = 'gl_verify_email';
    private const NONCE_FIELD = 'gl_verify_nonce';
    private const QUERY_PARAM = 'gl-verify-token';

    public function register(): void
    {
        add_action('template_redirect', [$this, 'maybeHandle']);
    }

    public function maybeHandle(): void
    {
        if (is_admin()) {
            return;
        }
        if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
            return;
        }
        if (function_exists('wp_doing_cron') && wp_doing_cron()) {
            return;
        }

        if (empty($_GET[self::QUERY_PARAM])) {
            return;
        }

        $token = sanitize_text_field(wp_unslash($_GET[self::QUERY_PARAM]));

        if (!preg_match('/^[a-f0-9]{24}$/', $token)) {
            $this->renderInvalid();
            exit;
        }

        $method = isset($_SERVER['REQUEST_METHOD'])
            ? strtoupper(sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'])))
            : 'GET';

        if ($method === 'POST') {
            $this->handleConfirm($token);
            exit;
        }

        $this->renderConfirm($token);
        exit;
    }

    private function handleConfirm(string $token): void
    {
        $nonce = isset($_POST[self::NONCE_FIELD])
            ? sanitize_text_field(wp_unslash($_POST[self::NONCE_FIELD]))
            : '';

        if (!wp_verify_nonce($nonce, self::NONCE_ACTION)) {
            $this->renderInvalid();
            return;
        }

        $success = false;

        try {
            $api_key = PluginSettings::getApiKey();
            $response = ApiClient::verifyEmail($api_key, $token);

            if (is_wp_error($response)) {
                Logger::debug('EmailVerificationPage: verifyEmail returned WP_Error: ' . $response->get_error_message());
            } else {
                $status = wp_remote_retrieve_response_code($response);
                if ($status >= 200 && $status < 300) {
                    $success = true;
                } else {
                    Logger::debug('EmailVerificationPage: verifyEmail non-2xx status: ' . $status);
                }
            }
        } catch (\Throwable $e) {
            Logger::debug('EmailVerificationPage: verifyEmail threw: ' . $e->getMessage());
        }

        if ($success) {
            $this->renderResult(
                'Email verified',
                'Email verified',
                'Your email address has been confirmed. You can now log in.'
            );
        } else {
            $this->renderResult(
                'Verification failed',
                'This link is no longer valid',
                'We could not verify your email. The link may have expired or already been used. Please request a new verification email and try again.'
            );
        }
    }

    private function renderConfirm(string $token): void
    {
        $action = add_query_arg(self::QUERY_PARAM, $token, home_url('/'));
        $nonce = wp_create_nonce(self::NONCE_ACTION);

        $inner = '<h1>Confirm your email</h1>'
            . '<p>Click the button below to confirm your email address.</p>'
            . '<form method="post" action="' . esc_url($action) . '">'
            . '<input type="hidden" name="' . esc_attr(self::QUERY_PARAM) . '" value="' . esc_attr($token) . '" />'
            . '<input type="hidden" name="' . esc_attr(self::NONCE_FIELD) . '" value="' . esc_attr($nonce) . '" />'
            . '<button type="submit" class="gl-verify-button">Confirm</button>'
            . '</form>';

        $this->renderShell('Confirm your email', $inner);
    }

    private function renderResult(string $title, string $heading, string $message): void
    {
        $inner = '<h1>' . esc_html($heading) . '</h1>'
            . '<p>' . esc_html($message) . '</p>';

        $this->renderShell($title, $inner);
    }

    private function renderInvalid(): void
    {
        $this->renderResult(
            'Link no longer valid',
            'This link is no longer valid',
            'This verification link is invalid or has expired. Please request a new verification email and try again.'
        );
    }

    private function renderShell(string $title, string $inner): void
    {
        if (function_exists('nocache_headers')) {
            nocache_headers();
        }

        echo '<!DOCTYPE html>' . "\n";
        echo '<html><head>';
        echo '<meta charset="utf-8" />';
        echo '<meta name="viewport" content="width=device-width, initial-scale=1" />';
        echo '<meta name="robots" content="noindex,nofollow" />';
        echo '<title>' . esc_html($title) . '</title>';
        echo '<style>'
            . 'body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;'
            . 'background:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:#1f2933;}'
            . '.gl-verify-card{background:#fff;max-width:420px;width:calc(100% - 32px);margin:16px;padding:32px;'
            . 'border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,0.08);text-align:center;}'
            . '.gl-verify-card h1{font-size:22px;margin:0 0 12px;}'
            . '.gl-verify-card p{font-size:15px;line-height:1.5;margin:0 0 24px;color:#52606d;}'
            . '.gl-verify-button{display:inline-block;border:0;cursor:pointer;background:#16a34a;color:#fff;'
            . 'font-size:16px;font-weight:600;padding:12px 28px;border-radius:8px;}'
            . '.gl-verify-button:hover{background:#15803d;}'
            . '</style>';
        echo '</head><body>';
        echo '<div class="gl-verify-card">' . $inner . '</div>';
        echo '</body></html>';
    }
}